package data

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/elastic/go-elasticsearch/v8/typedapi/types"
	"github.com/redis/go-redis/v9"
	"golang.org/x/sync/singleflight"
	"gorm.io/gorm"
	"review-service/dal/model"
	"review-service/dal/query"
	"strconv"
	"strings"
	"time"

	"review-service/internal/biz"

	"github.com/go-kratos/kratos/v2/log"
)

type ReviewRepo struct {
	data *Data
	log  *log.Helper
}

// NewReviewRepo .
func NewReviewRepo(data *Data, logger log.Logger) biz.ReviewRepo {
	return &ReviewRepo{
		data: data,
		log:  log.NewHelper(logger),
	}
}

func (r *ReviewRepo) Save(ctx context.Context, review *model.ReviewInfo) (*model.ReviewInfo, error) {
	err := r.data.
		Q.ReviewInfo.
		WithContext(ctx).
		Save(review)
	return review, err
}

// 根据订单id查询评价
func (r *ReviewRepo) GetReviewByOrderID(ctx context.Context, orderid int64) ([]*model.ReviewInfo, error) {
	return r.data.Q.ReviewInfo.WithContext(ctx).
		Where(r.data.Q.ReviewInfo.OrderID.Eq(orderid)).
		Find()
}

func (r *ReviewRepo) AuditReview(ctx context.Context, g *model.ReviewInfo) (*model.ReviewInfo, error) {
	updates := &model.ReviewInfo{
		Status:    g.Status,
		OpUser:    g.OpUser,
		OpReason:  g.OpReason,
		OpRemarks: g.OpRemarks,
	}
	_, err := r.data.Q.ReviewInfo.WithContext(ctx).Where(r.data.Q.ReviewInfo.ReviewID.Eq(g.ReviewID)).Updates(updates)
	if err != nil {
		return nil, err
	}
	return updates, nil

}

func (r *ReviewRepo) FindByReviewID(ctx context.Context, reviewid int64) (*model.ReviewInfo, error) {
	return r.data.Q.ReviewInfo.WithContext(ctx).Where(r.data.Q.ReviewInfo.ReviewID.Eq(reviewid)).First()
}

func (r *ReviewRepo) SaveReply(ctx context.Context, g *model.ReviewReplyInfo) (*model.ReviewReplyInfo, error) {
	//数据校验
	//1.1 数据合法性 （已经回复的不能再回复）
	review, err := r.data.Q.ReviewInfo.WithContext(ctx).Where(r.data.Q.ReviewInfo.ReviewID.Eq(g.ReviewID)).First()
	if err != nil {
		return nil, err
	}
	if review.HasReply == 1 {
		return nil, errors.New("该评论已回复")
	}
	//1.2 水平校验 （A商家只能回复自己商铺的评价）
	if review.StoreID != g.StoreID {
		return nil, errors.New("水平越权")
	}
	//2更新 评价表和评价回复表（事务操作）
	err = r.data.Q.Transaction(func(tx *query.Query) error {
		//回复表插入一条数据
		if err := tx.ReviewReplyInfo.WithContext(ctx).Save(g); err != nil {
			r.log.WithContext(ctx).Errorw("save review reply error", "err", err)
			return err
		}
		//评价表更新hasReply字段
		if _, err := tx.ReviewInfo.WithContext(ctx).
			Where(r.data.Q.ReviewInfo.ReviewID.Eq(g.ReviewID)).
			Update(tx.ReviewInfo.HasReply, 1); err != nil {
			r.log.WithContext(ctx).Errorw("save  reply update review error", "err", err)
			return err
		}
		return nil
	})
	//3返回
	return g, err
}

func (r *ReviewRepo) AppealReview(ctx context.Context, g *model.ReviewAppealInfo) (*model.ReviewAppealInfo, error) {
	appeal, err := r.data.Q.ReviewAppealInfo.WithContext(ctx).Where(r.data.Q.ReviewInfo.ReviewID.Eq(g.ReviewID)).First()

	//若商家已经申诉则查看状态是否>10
	//若商家没有申诉，则进行申诉
	if err != nil {
		if err != gorm.ErrRecordNotFound {
			return nil, err
		}
		review, err := r.data.Q.ReviewInfo.WithContext(ctx).Where(r.data.Q.ReviewInfo.ReviewID.Eq(g.ReviewID)).First()
		if err != nil {
			return nil, err
		}
		if review.StoreID != g.StoreID {
			return nil, errors.New("水平越权")
		}
		err = r.data.Q.ReviewAppealInfo.WithContext(ctx).Save(g)
		if err != nil {
			return nil, err
		}
		return g, err
	} else {
		if appeal.Status > 10 {
			return nil, errors.New("不允许商家重复申诉")
		}
		_, err := r.data.Q.ReviewAppealInfo.WithContext(ctx).Where(r.data.Q.ReviewInfo.ReviewID.Eq(g.ReviewID)).Updates(appeal)
		if err != nil {
			return nil, err
		}
		return g, err
	}
}

func (r *ReviewRepo) AuditAppeal(ctx context.Context, g *model.ReviewAppealInfo) error {
	appeal, err := r.data.Q.ReviewAppealInfo.WithContext(ctx).Where(r.data.Q.ReviewAppealInfo.ReviewID.Eq(g.ReviewID)).First()
	if err != nil {
		return err
	}
	if appeal.StoreID != g.StoreID {
		return errors.New("水平越权")
	}
	err = r.data.Q.Transaction(func(tx *query.Query) error {
		if g.Status == 20 {
			_, err = tx.ReviewInfo.WithContext(ctx).Update(tx.ReviewInfo.Status, 40)
		}
		_, err = tx.ReviewAppealInfo.WithContext(ctx).Updates(appeal)
		return err
	})
	return err
}

func (r *ReviewRepo) ListReviewByStoreID(ctx context.Context, storeid int64, offset, limit int32) ([]*biz.MyReviewInfo, error) {
	//去ES里面查询评价
	//1.先查询redis缓存
	//2.查询es
	//3.通过singleflight合并短时间大量的并发查询
	key := fmt.Sprintf("review:%d:%d:%d", storeid, offset, limit)
	b, err := r.getDataFromSingle(ctx, key)
	if err != nil {
		return nil, err
	}
	hm := new(types.HitsMetadata)
	if err := json.Unmarshal(b, hm); err != nil {
		return nil, err
	}
	list := make([]*biz.MyReviewInfo, 0, hm.Total.Value)
	for _, hit := range hm.Hits {
		tmp := &biz.MyReviewInfo{}
		if err := json.Unmarshal(hit.Source_, tmp); err != nil {
			r.log.Errorf("Unmarshal err %v", err)
			continue
		}
		list = append(list, tmp)
	}
	//反序列化数据
	return list, nil
}

var g singleflight.Group

func (r *ReviewRepo) getDataFromSingle(ctx context.Context, key string) (bytes []byte, err error) {
	v, err, _ := g.Do(key, func() (interface{}, error) {
		//1.查缓存
		data, err := r.getDataFromCache(ctx, key)
		if err == nil {
			return data, nil
		}
		if err == redis.Nil {
			//缓存中没有这个key
			data, err := r.getDataFromES(ctx, key)
			if err == nil {
				return data, r.setCache(ctx, key, data)
			}
			return nil, err
		}
		//查缓存失败了,不向下传到压力
		return nil, err
	})
	//r.log.Debugf("getDataFromSingle: v:%v err:%v", v, err)
	if err != nil {
		return nil, err
	}
	return v.([]byte), nil
}
func (r *ReviewRepo) getDataFromCache(ctx context.Context, key string) ([]byte, error) {
	r.log.Debugf("getDataFromCache: key=%s", key)
	return r.data.rdb.Get(ctx, key).Bytes()
}
func (r *ReviewRepo) setCache(ctx context.Context, key string, data []byte) error {
	return r.data.rdb.Set(ctx, key, data, time.Second*60).Err()
}
func (r *ReviewRepo) getDataFromES(ctx context.Context, key string) ([]byte, error) {
	values := strings.Split(key, ":")
	if len(values) < 4 {
		return nil, errors.New("invalid key")
	}
	index, storeid, offsetstr, limitstr := values[0], values[1], values[2], values[3]
	offset, err := strconv.Atoi(offsetstr)
	if err != nil {
		return nil, err
	}
	limit, err := strconv.Atoi(limitstr)
	if err != nil {
		return nil, err
	}
	res, err := r.data.es.Search().
		Index(index).
		From(offset).
		Size(limit).
		Query(&types.Query{
			Bool: &types.BoolQuery{
				Filter: []types.Query{
					{
						Term: map[string]types.TermQuery{
							"store_id": {Value: storeid},
						},
					},
				},
			},
		}).Do(ctx)
	if err != nil {
		return nil, err
	}
	return json.Marshal(res.Hits)
}

func (r *ReviewRepo) ListByHello(context.Context, string) ([]*model.ReviewInfo, error) {
	return nil, nil
}

func (r *ReviewRepo) ListAll(context.Context) ([]*model.ReviewInfo, error) {
	return nil, nil
}
